Appearance
2777. 日期范围生成器
ts
function* dateRangeGenerator(
start: string,
end: string,
step: number
): Generator<string> {
const startTime = Date.parse(start)
const endTime = Date.parse(end)
if (isNaN(startTime) || isNaN(endTime)) {
throw new Error('Invalid start or end date')
}
if (step <= 0) {
throw new Error('Step must be greater than 0')
}
const ONE_DAY = 86400000 // 24 * 60 * 60 * 1000
const stepTime = step * ONE_DAY
for (let time = startTime; time <= endTime; time += stepTime) {
const d = new Date(time)
const y = d.getFullYear()
const m = (d.getMonth() + 1).toString().padStart(2, '0')
const day = d.getDate().toString().padStart(2, '0')
yield `${y}-${m}-${day}`
}
}
/**
* const g = dateRangeGenerator('2023-04-01', '2023-04-04', 1);
* g.next().value; // '2023-04-01'
* g.next().value; // '2023-04-02'
* g.next().value; // '2023-04-03'
* g.next().value; // '2023-04-04'
* g.next().done; // true
*/时间戳 vs 日期对象
这个实现以毫秒级时间戳为中间表示,for 循环中每次加 step * ONE_DAY 毫秒。这种做法的优点是不需要处理月份跨度和闰年——时间戳的加法天然处理了这些。
但也有一个隐患:夏令时。如果日期范围跨越 DST 切换日,一天可能不是 86400000 毫秒(23 或 25 小时)。这种情况下,new Date(time) 可能产生意料之外的日期。不过对于大多数场景(尤其是只关心日期不关心时间的需求),这个实现已经足够。
惰性生成
Generator 的关键优势:不需要预先生成整个数组。如果日期范围是一整年,step=1 会生成 365 个日期字符串。用 Generator 可以在遍历到某一天时再计算,不必一次性分配 365 个字符串的内存。
错误抛出时机
注意 throw 在 Generator 函数体的顶部——它们惰性执行。创建 Generator 对象(调用 dateRangeGenerator(...))不会执行函数体,只有第一次 .next() 才会触发参数校验。这意味着错误不会在创建 Generator 时立即抛出,而是在实际迭代时。这和普通函数的错误处理直觉不同。
